package uy.org.softtech.cssuite.entidad.finanzas;

import static uy.org.softtech.cssuite.entidad.Entidad.FINANZAS_NAMESPACE;
import static uy.org.softtech.cssuite.helpers.HelperFechaHora.fechaLarga;
import static uy.org.softtech.cssuite.helpers.HelperFinanzas.formatearImporte;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlType;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonManagedReference;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonRootName;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;

import uy.org.softtech.cssuite.entidad.Entidad;
import uy.org.softtech.cssuite.entidad.cliente.Cliente;
import uy.org.softtech.cssuite.helpers.HelperFechaHora;
import uy.org.softtech.cssuite.helpers.HelperFinanzas;

/**
 * <p>Entidad que representa a una cuenta de cliente. También mapea la declaración del tipo de dato XML 'cuenta'.</p>
 * 
 * <p>El siguiente fragmento de esquema especifica el contenido esperado y representado por los atributos de esta clase.</p>
 *  
 * <pre>
 * &lt;xsd:complexType name="cuenta">
 *  &lt;xsd:all>
 *   &lt;xsd:element name="id" type="xsd:ID"/>
 *   &lt;xsd:element name="estado" type="{http://cssuite.softtech.org.uy/esquemas/tipos}estadoCuenta"/>
 *   &lt;xsd:element name="creditos" type="xsd:double"/>
 *   &lt;xsd:element name="debitos" type="xsd:double"/>
 *   &lt;xsd:element name="balance" type="xsd:double"/>
 *   &lt;xsd:element name="fechaCreacion" type="xsd:dateTime"/>
 *   &lt;xsd:element name="fechaActivacion" type="xsd:dateTime" nillable="true"/>
 *   &lt;xsd:element name="fechaDesactivacion" type="xsd:dateTime" nillable="true"/>
 *   &lt;xsd:element name="fechaCierre" type="xsd:dateTime" nillable="true"/>
 *   &lt;xsd:element name="fechaEstadoCuenta" type="xsd:dateTime" nillable="true"/>
 *   &lt;xsd:element name="comentarios" type="{http://cssuite.softtech.org.uy/esquemas/tipos}comentario" minOccurs="0"/>
 *   &lt;xsd:element name="movimientos">
 *    &lt;xsd:complexType>
 *     &lt;xsd:sequence>
 *      &lt;xsd:element name="movimiento" type="{http://cssuite.softtech.org.uy/esquemas/entidades}permiso" maxOccurs="unbounded" />
 *     &lt;/xsd:sequence>
 *    &lt;/xsd:complexType>
 *   &lt;/xsd:element>
 *   &lt;xsd:element name="pagos">
 *    &lt;xsd:complexType>
 *     &lt;xsd:sequence>
 *      &lt;xsd:element name="pago" type="{http://cssuite.softtech.org.uy/esquemas/entidades}pago" maxOccurs="unbounded" />
 *     &lt;/xsd:sequence>
 *    &lt;/xsd:complexType>
 *   &lt;/xsd:element>
 *   &lt;xsd:element name="facturas">
 *    &lt;xsd:complexType>
 *     &lt;xsd:sequence>
 *      &lt;xsd:element name="factura" type="{http://cssuite.softtech.org.uy/esquemas/entidades}factura" maxOccurs="unbounded" />
 *     &lt;/xsd:sequence>
 *    &lt;/xsd:complexType>
 *   &lt;/xsd:element>
 *  &lt;/xsd:all>
 * &lt;/xsd:complexType>
 * 
 * </pre>
 *
 * @author fabian.lobo
 */
@Entity
@JsonRootName(value = "cuenta")
@JsonSerialize(include = Inclusion.NON_NULL)
@Table(name = "CSS_CUENTA")
@XmlType(name = "cuenta", namespace = FINANZAS_NAMESPACE, propOrder = {})
public class Cuenta extends Entidad implements Serializable {

	private static final long serialVersionUID = -2481900318176662781L;
	
	/**
	 * Enumerado para agrupar los estados de las cuentas de cliente.
	 * 
	 * @author fabian.lobo
	 */
	@XmlEnum
	@XmlType(name = "estadoCuenta", namespace = FINANZAS_NAMESPACE)
	public enum Estado {
		CREADA, ACTIVA, INACTIVA, CANCELADA, CERRADA;
	}
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "ID_CUENTA")
	@XmlElement(name = "id", namespace = FINANZAS_NAMESPACE, required = true)
	private Long id;
	
	@Column(name = "ESTADO", length = 10, nullable = false)
	@Enumerated(EnumType.STRING)
	@XmlElement(name = "estado", namespace = FINANZAS_NAMESPACE, required = true)
	private Estado estado;
	
	@ForeignKey(name = "FK_CUENTA_CLIENTE")
	@JoinColumn(name = "ID_CLIENTE", nullable = false)
	@JsonIgnore
	@ManyToOne(optional = false, fetch = FetchType.EAGER)
	@XmlIDREF
	private Cliente cliente;
	
	@Column(name = "CREDITOS", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	@XmlElement(name = "creditos", namespace = FINANZAS_NAMESPACE, required = true)
	private BigDecimal creditos = new BigDecimal("0.00");
	
	@Column(name = "DEBITOS", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	@XmlElement(name = "debitos", namespace = FINANZAS_NAMESPACE, required = true)
	private BigDecimal debitos = new BigDecimal("0.00");
	
	@Column(name = "BALANCE", nullable = false, precision = 10, scale = 2)
	@JsonIgnore
	@XmlElement(name = "balance", namespace = FINANZAS_NAMESPACE, required = true)
	private BigDecimal balance = new BigDecimal("0.00");
	
	@Column(name = "FECHA_CREACION", nullable = false)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "fechaCreacion", namespace = FINANZAS_NAMESPACE, required = true)
	private Date fechaCreacion;
	
	@Column(name = "FECHA_ACTIVACION", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "fechaActivacion", namespace = FINANZAS_NAMESPACE, required = true, nillable = true)
	private Date fechaActivacion;
	
	@Column(name = "FECHA_DESACTIVACION", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "fechaDesactivacion", namespace = FINANZAS_NAMESPACE, required = true, nillable = true)
	private Date fechaDesactivacion;
	
	@Column(name = "FECHA_CIERRE", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "fechaCierre", namespace = FINANZAS_NAMESPACE, required = true, nillable = true)
	private Date fechaCierre;
	
	@Column(name = "FECHA_ESTADO_CUENTA", nullable = true)
	@JsonIgnore
	@Temporal(TemporalType.TIMESTAMP)
	@XmlElement(name = "fechaEstadoCuenta", namespace = FINANZAS_NAMESPACE, required = true, nillable = true)
	private Date fechaEstadoCuenta;
	
	@Column(name = "COMENTARIOS", length = 500, nullable = true)
	@XmlElement(name = "comentarios", namespace = FINANZAS_NAMESPACE, required = false)
	private String comentarios;
	
	@JsonManagedReference
	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "cuenta")
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy(value = "fecha DESC, id DESC")
	@XmlElementWrapper(name = "movimientos", namespace = FINANZAS_NAMESPACE, required = true)
	@XmlElement(name = "movimiento", namespace = FINANZAS_NAMESPACE, required = true)
	private Set<Movimiento> movimientos;
	
	@JsonManagedReference
	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "cuenta")
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy(value = "id DESC")
	@XmlElementWrapper(name = "pagos", namespace = FINANZAS_NAMESPACE, required = true)
	@XmlElement(name = "pago", namespace = FINANZAS_NAMESPACE, required = true)
	private Set<Pago> pagos;
	
	@JsonManagedReference
	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "cuenta")
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy(value = "identificador DESC")
	@XmlElementWrapper(name = "facturas", namespace = FINANZAS_NAMESPACE, required = true)
	@XmlElement(name = "factura", namespace = FINANZAS_NAMESPACE, required = true)
	private Set<Factura> facturas;
		
	/*
	 *  GETTERS
	 */
	public Long getId() {
		return id;
	}
	public Estado getEstado() {
		return estado;
	}
	public Cliente getCliente() {
		return cliente;
	}
	public BigDecimal getCreditos() {
		return creditos;
	}
	public BigDecimal getDebitos() {
		return debitos;
	}
	public BigDecimal getBalance() {
		return balance;
	}
	public Date getFechaCreacion() {
		return fechaCreacion;
	}
	public Date getFechaActivacion() {
		return fechaActivacion;
	}
	public Date getFechaDesactivacion() {
		return fechaDesactivacion;
	}
	public Date getFechaCierre() {
		return fechaCierre;
	}
	public Date getFechaEstadoCuenta() {
		return fechaEstadoCuenta;
	}
	public String getComentarios() {
		return comentarios;
	}
	public Set<Movimiento> getMovimientos() {
		return movimientos;
	}
	public Set<Pago> getPagos() {
		return pagos;
	}
	public Set<Factura> getFacturas() {
		return facturas;
	}
	
	/*
	 *  SETTERS
	 */
	public void setId(Long id) {
		this.id = id;
	}
	public void setEstado(Estado estado) {
		this.estado = estado;
	}
	public void setCliente(Cliente cliente) {
		this.cliente = cliente;
	}
	public void setCreditos(BigDecimal creditos) {
		this.creditos = creditos;
	}
	public void setDebitos(BigDecimal debitos) {
		this.debitos = debitos;
	}
	public void setBalance(BigDecimal balance) {
		this.balance = balance;
	}
	public void setFechaCreacion(Date fechaCreacion) {
		this.fechaCreacion = fechaCreacion;
	}
	public void setFechaActivacion(Date fechaActivacion) {
		this.fechaActivacion = fechaActivacion;
	}
	public void setFechaDesactivacion(Date fechaDesactivacion) {
		this.fechaDesactivacion = fechaDesactivacion;
	}
	public void setFechaCierre(Date fechaCierre) {
		this.fechaCierre = fechaCierre;
	}
	public void setFechaEstadoCuenta(Date fechaEstadoCuenta) {
		this.fechaEstadoCuenta = fechaEstadoCuenta;
	}
	public void setComentarios(String comentarios) {
		this.comentarios = comentarios;
	}	
	public void setMovimientos(Set<Movimiento> movimientos) {
		this.movimientos = movimientos;
	}
	public void setPagos(Set<Pago> pagos) {
		this.pagos = pagos;
	}
	public void setFacturas(Set<Factura> facturas) {
		this.facturas = facturas;
	}
	
	/*
	 *  CONVERSORES
	 */
	/**
	 * Convierte la fecha de creación a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaCreacion")
	public String convierteFechaCreacion() {
		return fechaLarga(getFechaCreacion());
	}
	
	/**
	 * Convierte la fecha de activación a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaActivacion")
	public String convierteFechaActivacion() {
		return fechaLarga(getFechaActivacion());
	}
	
	/**
	 * Convierte la fecha de desactivación a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaDesactivacion")
	public String convierteFechaDesactivacion() {
		return fechaLarga(getFechaDesactivacion());
	}
	
	/**
	 * Convierte la fecha de cierre a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaCierre")
	public String convierteFechaCierre() {
		return fechaLarga(getFechaCierre());
	}
	
	/**
	 * Convierte la fecha del último estado de cuenta a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFechaHora#fechaLarga()
	 */
	@JsonProperty(value = "fechaEstadoCuenta")
	public String convierteFechaEstadoCuenta() {
		return fechaLarga(getFechaEstadoCuenta());
	}
	
	/**
	 * Convierte el importe de créditos a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "creditos")
	public String convierteCreditos() {
		return formatearImporte(getCreditos());
	}
	
	/**
	 * Convierte el importe de débitos a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "debitos")
	public String convierteDebitos() {
		return formatearImporte(getDebitos());
	}
	
	/**
	 * Convierte el importe de balance a texto
	 * 
	 * @author fabian.lobo
	 * @see HelperFinanzas#formatearImporte()
	 */
	@JsonProperty(value = "balance")	
	public String convierteBalance() {
		return formatearImporte(getBalance());
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 7;
		result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		Cuenta otro = (Cuenta) obj;
		if (id != null && otro.id != null && id.equals(otro.id)) {
			return true;
		}
		return false;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Cuenta [");
		builder.append("id=" + getId() + ", ");
		builder.append("estado=" + getEstado() + ", ");
		builder.append("cliente" + getCliente() + ", ");
		builder.append("creditos" + convierteCreditos() + ", ");
		builder.append("debitos" + convierteDebitos() + ", ");
		builder.append("balance" + convierteBalance() + ", ");
		builder.append("fechaCreacion" + convierteFechaCreacion() + ", ");
		builder.append("fechaActivacion" + convierteFechaActivacion() + ", ");
		builder.append("fechaDesactivacion" + convierteFechaDesactivacion() + ", ");
		builder.append("fechaCierre" + convierteFechaCierre() + ", ");
		builder.append("fechaEstadoCuenta" + convierteFechaEstadoCuenta() + ", ");
		builder.append("comentarios=" + getComentarios());
		builder.append("]");
		return builder.toString();
	}

	@Override
	public String toString(boolean incluirColecciones) {
		StringBuilder result = new StringBuilder(toString());
		
		if(incluirColecciones) {
			int offset = result.length() - 2;
			StringBuilder builder = new StringBuilder(", ");
			builder.append("movimientos=" + getMovimientos() + ", ");
			builder.append("pagos=" + getPagos() + ", ");
			builder.append("facturas=" + getFacturas());
			result.insert(offset, builder.toString());
		}	
				
		return result.toString();
	}

}
